/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// TGUI - Texus' Graphical User Interface
// Copyright (C) 2012-2025 Bruno Van de Velde (vdv_b@tgui.eu)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
//    you must not claim that you wrote the original software.
//    If you use this software in a product, an acknowledgment
//    in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
//    and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef TGUI_COPIED_PTR_HPP
#define TGUI_COPIED_PTR_HPP

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <TGUI/Config.hpp>

#include <memory>

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace tgui
{
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// @internal
    /// @brief Used by makeCopied to construct a CopiedPtr object
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    struct CopiedPtrEmplaceTag { explicit CopiedPtrEmplaceTag() = default; };

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// @brief Copyable smart pointer template
    ///
    /// The smart pointer behaves as a normal value (i.e. copying the pointer also copies the underlying value),
    /// but this object can also represent a nullptr.
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    template <typename T>
    class CopiedPtr
    {
    public:

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Default constructor that initializes the object to a nullptr
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        CopiedPtr()
        {
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Constructor that accepts a nullptr as parameter
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        CopiedPtr(std::nullptr_t)
        {
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Constructor that creates a new object
        ///
        /// This constructor is used by the makeCopied function, which is the intended way to construct a CopiedPtr.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        template <typename... Args>
        CopiedPtr(CopiedPtrEmplaceTag, Args&&... args) :
            m_ptr(std::make_unique<T>(args...)),
            m_cloner([](const void* val) -> void* { return new T(*static_cast<const T*>(val)); })
        {
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Copy constructor
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        CopiedPtr(const CopiedPtr& other) :
            m_ptr(other.m_ptr ? static_cast<T*>(other.m_cloner(other.m_ptr.get())) : nullptr),
            m_cloner(other.m_cloner)
        {
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Copy constructor that takes a pointer to a derived object
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        template <typename U>
        CopiedPtr(const CopiedPtr<U>& other) :
            m_ptr(other.m_ptr ? static_cast<T*>(other.m_cloner(other.m_ptr.get())) : nullptr),
            m_cloner(other.m_cloner)
        {
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Move constructor
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        CopiedPtr(CopiedPtr&& other) :
            m_ptr(std::move(other.m_ptr)),
            m_cloner(std::move(other.m_cloner))
        {
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Move constructor that takes a pointer to a derived object
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        template <typename U>
        CopiedPtr(CopiedPtr&& other) :
            m_ptr(std::move(other.m_ptr)),
            m_cloner(std::move(other.m_cloner))
        {
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Exchanges the values of *this and other
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void swap(CopiedPtr& other)
        {
            std::swap(m_ptr, other.m_ptr);
            std::swap(m_cloner, other.m_cloner);
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Copy assignment operator
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        CopiedPtr& operator=(const CopiedPtr& other)
        {
            if (this != &other)
                CopiedPtr(other).swap(*this);
            return *this;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Copy assignment operator for a pointer to a derived object
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        template <typename U>
        CopiedPtr& operator=(const CopiedPtr<U>& other)
        {
            CopiedPtr(other).swap(*this);
            return *this;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Move assignment operator
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        CopiedPtr& operator=(CopiedPtr&& other)
        {
            if (this != &other)
                CopiedPtr(std::move(other)).swap(*this);
            return *this;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Move assignment operator for a pointer to a derived object
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        template <typename U>
        CopiedPtr& operator=(CopiedPtr<U>&& other)
        {
            CopiedPtr(std::move(other)).swap(*this);
            return *this;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Dereferences the pointer
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        T& operator*()
        {
            return *m_ptr;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Dereferences the pointer for member access
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        T* operator->() const
        {
            return m_ptr.get();
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns whether the pointer differs from a nullptr
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        operator bool() const
        {
            return m_ptr != nullptr;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns a raw pointer to the underlying object
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        T* get() const
        {
            return m_ptr.get();
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Reset the pointer to a nullptr
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void reset()
        {
            m_ptr.reset();
            m_cloner = nullptr;
        }

    private:
        std::unique_ptr<T> m_ptr;

        // When m_ptr contains a pointer to a base class, we still need some way to copy the derived class
        void*(*m_cloner)(const void*) = nullptr;

        // The copy constructor needs access to classes with a different template parameter
        template <typename U>
        friend class CopiedPtr;
    };

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// @brief Creates a CopiedPtr object that contains a value
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    template <typename T, typename... Args>
    CopiedPtr<T> makeCopied(Args&&... args)
    {
        return CopiedPtr<T>(CopiedPtrEmplaceTag{}, std::forward<Args>(args)...);
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#endif // TGUI_COPIED_PTR_HPP
